package org.uma.jmetal.lab.experiment.component;

import org.uma.jmetal.lab.experiment.Experiment;
import org.uma.jmetal.lab.experiment.ExperimentComponent;
import org.uma.jmetal.lab.experiment.util.ExperimentAlgorithm;
import org.uma.jmetal.lab.experiment.util.ExperimentProblem;
import org.uma.jmetal.problem.doubleproblem.DoubleProblem;
import org.uma.jmetal.solution.AbstractSolution;
import org.uma.jmetal.solution.Solution;
import org.uma.jmetal.solution.doublesolution.DoubleSolution;
import org.uma.jmetal.util.JMetalException;
import org.uma.jmetal.util.JMetalLogger;
import org.uma.jmetal.util.archive.impl.NonDominatedSolutionListArchive;
import org.uma.jmetal.util.fileoutput.SolutionListOutput;
import org.uma.jmetal.util.front.Front;
import org.uma.jmetal.util.front.impl.ArrayFront;
import org.uma.jmetal.util.solutionattribute.impl.GenericSolutionAttribute;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * This class computes the reference Pareto set and front from a set of data files containing the
 * variable (VARx.tsv file) and objective (FUNx.tsv) values. A requirement is that the variable
 * values MUST correspond to {@link DoubleSolution} solutions, i.e., the solved problems must be
 * instances of {@link DoubleProblem}.
 *
 * <p>Once the algorithms of an org.uma.jmetal.experiment have been executed through running an
 * instance of class {@link ExecuteAlgorithms}, all the obtained fronts of all the algorithms are
 * gathered per problem; then, the dominated solutions are removed thus yielding to the reference
 * Pareto front.
 *
 * <p>By default, the files are stored in a directory called "referenceFront", which is located in
 * the org.uma.jmetal.experiment base directory. The following files are generated per problem: -
 * "problemName.pf": the reference Pareto front. - "problemName.ps": the reference Pareto set (i.e.,
 * the variable values of the solutions of the reference Pareto front. -
 * "problemName.algorithmName.pf": the objectives values of the contributed solutions by the
 * algorithm called "algorithmName" to "problemName.pf" - "problemName.algorithmName.ps": the
 * variable values of the contributed solutions by the algorithm called "algorithmName" to
 * "problemName.ps"
 *
 * @author Antonio J. Nebro <antonio@lcc.uma.es>
 *
 *此类从包含以下内容的一组数据文件中计算参考帕累托集和前列
 *变量（VARx.tsv文件）和目标（FUNx.tsv）值。要求是变量
 *值必须对应于{@link DoubleSolution}解决方案，即解决的问题必须是
 * {@link DoubleProblem}的实例。
 *
 * <p> org.uma.jmetal.experiment的算法一旦通过运行
 *类{@link ExecuteAlgorithms}的实例，所有算法的所有获得的前沿都是
 *收集每个问题；然后，除去主要溶液，从而得到参考值
 *帕累托前面。
 *
 * <p>默认情况下，文件存储在名为“ referenceFront”的目录中，该目录位于
 * org.uma.jmetal.experiment基本目录。每个问题生成以下文件：-
 *“ problemName.pf”：引用帕累托前沿。 -“ problemName.ps”：参考帕累托集（即，
 *参考帕累托前沿的解的变量值。 --
 *“ problemName.algorithmName.pf”：贡献的解决方案的目标值
 *算法称为“ algorithmName”到“ problemName.pf”-“ problemName.algorithmName.ps”：
 *通过称为“ algorithmName”的算法将贡献解的变量值
 *“ problemName.ps”
 *
 */
public class GenerateReferenceParetoSetAndFrontFromDoubleSolutions implements ExperimentComponent {
  private final Experiment<?, ?> experiment;

  public GenerateReferenceParetoSetAndFrontFromDoubleSolutions(
      Experiment<?, ?> experimentConfiguration) {
    this.experiment = experimentConfiguration;
  }

  /** The run() method creates de output directory and compute the fronts */
  @Override
  public void run() throws IOException {
    String outputDirectoryName = experiment.getReferenceFrontDirectory();
    createOutputDirectory(outputDirectoryName);

    for (ExperimentProblem<?> problem : experiment.getProblemList()) {
      List<DummyDoubleSolution> nonDominatedSolutions = getNonDominatedSolutions(problem);

      writeReferenceFrontFile(outputDirectoryName, problem, nonDominatedSolutions);
      writeReferenceSetFile(outputDirectoryName, problem, nonDominatedSolutions);

      writeFilesWithTheSolutionsContributedByEachAlgorithm(
          outputDirectoryName, problem, nonDominatedSolutions);
    }
  }

  private void writeFilesWithTheSolutionsContributedByEachAlgorithm(
      String outputDirectoryName,
      ExperimentProblem<?> problem,
      List<DummyDoubleSolution> nonDominatedSolutions) {
    GenericSolutionAttribute<DoubleSolution, String> solutionAttribute =
        new GenericSolutionAttribute<DoubleSolution, String>();

    for (ExperimentAlgorithm<?, ?> algorithm : experiment.getAlgorithmList()) {
      List<DoubleSolution> solutionsPerAlgorithm = new ArrayList<>();
      for (DoubleSolution solution : nonDominatedSolutions) {
        if (algorithm.getAlgorithmTag().equals(solutionAttribute.getAttribute(solution))) {
          solutionsPerAlgorithm.add(solution);
        }
      }

      new SolutionListOutput(solutionsPerAlgorithm)
          .printObjectivesToFile(
              outputDirectoryName
                  + "/"
                  + problem.getTag()
                  + "."
                  + algorithm.getAlgorithmTag()
                  + ".rf",
              ",");
      new SolutionListOutput(solutionsPerAlgorithm)
          .printVariablesToFile(
              outputDirectoryName
                  + "/"
                  + problem.getTag()
                  + "."
                  + algorithm.getAlgorithmTag()
                  + ".rs",
              ",");
    }
  }

  private void writeReferenceFrontFile(
      String outputDirectoryName,
      ExperimentProblem<?> problem,
      List<DummyDoubleSolution> nonDominatedSolutions) {
    String referenceFrontFileName = outputDirectoryName + "/" + problem.getReferenceFront();

    new SolutionListOutput(nonDominatedSolutions)
        .printObjectivesToFile(referenceFrontFileName, ",");
  }

  private void writeReferenceSetFile(
      String outputDirectoryName,
      ExperimentProblem<?> problem,
      List<DummyDoubleSolution> nonDominatedSolutions) {
    String referenceSetFileName = outputDirectoryName + "/" + problem.getTag() + ".ps";
    new SolutionListOutput(nonDominatedSolutions).printVariablesToFile(referenceSetFileName, ",");
  }

  /**
   * Create a list of non dominated {@link DoubleSolution} solutions from the FUNx.tsv and VARx.tsv
   * files that must have been previously obtained (probably by invoking the {@link
   * ExecuteAlgorithms#run} method).
   *
   * @param problem
   * @return
   * @throws FileNotFoundException
   */
  private List<DummyDoubleSolution> getNonDominatedSolutions(ExperimentProblem<?> problem)
      throws FileNotFoundException {
    NonDominatedSolutionListArchive<DummyDoubleSolution> nonDominatedSolutionArchive =
        new NonDominatedSolutionListArchive<>();

    for (ExperimentAlgorithm<?, ?> algorithm :
        experiment.getAlgorithmList().stream()
            .filter(s -> s.getProblemTag().equals(problem.getTag()))
            .collect(Collectors.toCollection(ArrayList::new))) {
      String problemDirectory =
          experiment.getExperimentBaseDirectory()
              + "/data/"
              + algorithm.getAlgorithmTag()
              + "/"
              + problem.getTag();

      String frontFileName =
          problemDirectory
              + "/"
              + experiment.getOutputParetoFrontFileName()
              + algorithm.getRunId()
              + ".dat";
      String paretoSetFileName =
          problemDirectory
              + "/"
              + experiment.getOutputParetoSetFileName()
              + algorithm.getRunId()
              + ".dat";

      Front frontWithObjectiveValues = new ArrayFront(frontFileName, ",");
      Front frontWithVariableValues = new ArrayFront(paretoSetFileName, ",");
      List<DummyDoubleSolution> solutionList =
          createSolutionListFrontFiles(
              algorithm.getAlgorithmTag(), frontWithVariableValues, frontWithObjectiveValues);
      for (DummyDoubleSolution solution : solutionList) {
        nonDominatedSolutionArchive.add(solution);
      }
    }

    return nonDominatedSolutionArchive.getSolutionList();
  }

  /**
   * Create the output directory where the result files will be stored
   *
   * @param outputDirectoryName
   * @return
   */
  private File createOutputDirectory(String outputDirectoryName) {
    File outputDirectory;
    outputDirectory = new File(outputDirectoryName);
    if (!outputDirectory.exists()) {
      boolean result = new File(outputDirectoryName).mkdir();
      JMetalLogger.logger.info("Creating " + outputDirectoryName + ". Status = " + result);
    }

    return outputDirectory;
  }

  /**
   * @param algorithmName
   * @param frontWithVariableValues
   * @param frontWithObjectiveValues
   * @return
   */
  private List<DummyDoubleSolution> createSolutionListFrontFiles(
      String algorithmName, Front frontWithVariableValues, Front frontWithObjectiveValues) {
    if (frontWithVariableValues.getNumberOfPoints()
        != frontWithObjectiveValues.getNumberOfPoints()) {
      throw new JMetalException(
          "The number of solutions in the variable and objective fronts are not equal");
    } else if (frontWithObjectiveValues.getNumberOfPoints() == 0) {
      throw new JMetalException("The front of solutions is empty");
    }

    GenericSolutionAttribute<DummyDoubleSolution, String> solutionAttribute =
        new GenericSolutionAttribute<DummyDoubleSolution, String>();

    int numberOfVariables = frontWithVariableValues.getPointDimensions();
    int numberOfObjectives = frontWithObjectiveValues.getPointDimensions();

    List<DummyDoubleSolution> solutionList = new ArrayList<>();
    for (int i = 0; i < frontWithVariableValues.getNumberOfPoints(); i++) {
      DummyDoubleSolution solution = new DummyDoubleSolution(numberOfVariables, numberOfObjectives);
      for (int vars = 0; vars < numberOfVariables; vars++) {
        solution.setVariable(vars, frontWithVariableValues.getPoint(i).getValues()[vars]);
      }
      for (int objs = 0; objs < numberOfObjectives; objs++) {
        solution.setObjective(objs, frontWithObjectiveValues.getPoint(i).getValues()[objs]);
      }

      solutionAttribute.setAttribute(solution, algorithmName);
      solutionList.add(solution);
    }

    return solutionList;
  }

  @SuppressWarnings("serial")
  private static class DummyDoubleSolution extends AbstractSolution<Double>
      implements DoubleSolution {

    public DummyDoubleSolution(int numberOfVariables, int numberOfObjectives) {
      super(numberOfVariables, numberOfObjectives);
    }

    @Override
    public Solution<Double> copy() {
      return null;
    }

    @Override
    public Double getLowerBound(int index) {
      return null;
    }

    @Override
    public Double getUpperBound(int index) {
      return null;
    }
  }
}
